<!-- prettier-ignore -->
<template>
  <div v-loading="boxLoading" class="dialog-box" element-loading-background="rgba(0, 0, 0, 0.8)"
       element-loading-spinner="el-icon-loading" element-loading-text="正在加载...">
    <div class="box-content" style="width: 667px;position:relative">
      <div class="content-header">
        <span>倾斜模型转换</span>
        <i class="el-icon-close" style="font-size: 16px" @click="closeModelDialog"></i>
      </div>
      <div class="content-body">
        <el-form class="form-center">
          <div class="form-item-box">
            <el-form-item label="输入设置">
              <el-button class="grey-hover-primary-button" style="width: 117px" @click="addInputSetting">追加
              </el-button>
              <el-button class="grey-hover-primary-button" style="width: 117px" @click="removeInputSetting">移除
              </el-button>
              <el-button class="grey-hover-primary-button" style="width: 117px" @click="importConf">导入osgb.conf
              </el-button>
              <el-button class="grey-hover-primary-button" style="width: 117px" @click="updateOsgb">替换</el-button>
            </el-form-item>
          </div>
          <div class="form-item-box">
            <div class="item-title">
              转换参数
            </div>
            <el-row :gutter="10" style="width: 100%">
              <el-col :span="16">
                <wp-scrollbar :maxHeight="400" scrollbarColor="rgba(255,255,255,0.5)">
                  <div v-for="item in inputSettings" :key="item.id" class="input-set-box">
                    <el-form-item label="输入路径">
                      <el-input v-model="item.path" disabled placeholder="请选择倾斜模型的DATA文件夹"
                                style="width: 217px"></el-input>
                      <el-button :disabled="item.onlyRead" type="primary" @click="openFolderSelect(item.id, 'input')">
                        选择文件
                      </el-button>
                      <prompt-icon :content="osgRootDirTip"></prompt-icon>
                    </el-form-item>
                    <el-form-item label="空间参考">
                      <el-input v-model="item.metadata.srs" disabled placeholder="请输入空间参考"
                                style="width: 217px"></el-input>
                      <el-button :disabled="item.onlyRead" style="width:80px" type="primary"
                                 @click="modifyCoordinateSystem(item.id)">
                        设置
                      </el-button>
                    </el-form-item>
                    <el-form-item label="原点坐标">
                      <el-input v-model="item.metadata.originPoint" :disabled="item.onlyRead"
                                placeholder="请输入原点坐标"
                                style="width: 217px"></el-input>
                    </el-form-item>
                  </div>
                </wp-scrollbar>
              </el-col>
              <el-col :span="8">
                <el-form-item label="重建法线">
                  <el-select v-model="osgConversionParams.options.reCreateNormal" :disabled="onlyRead">
                    <el-option :value="true" label="是">
                    </el-option>
                    <el-option :value="false" label="否">
                    </el-option>
                  </el-select>
                  <prompt-icon :content="reCreateNormalTip"></prompt-icon>
                </el-form-item>
                <el-form-item label="双面转换">
                  <el-select v-model="osgConversionParams.options.doubleSided" :disabled="onlyRead">
                    <el-option :value="true" label="是">
                    </el-option>
                    <el-option :value="false" label="否">
                    </el-option>
                  </el-select>
                  <prompt-icon :content="doubleSidedTip"></prompt-icon>
                </el-form-item>
                <el-form-item label="">
                  <div style="text-align: right;width: 100%">
                    <el-button size="mini" type="primary" @click="advanceShow = true">高级选项</el-button>
                  </div>
                </el-form-item>
              </el-col>
            </el-row>
          </div>
          <div class="form-item-box">
            <div class="item-title">
              合并参数
            </div>
            <el-form-item label="合并方式">
              <el-select v-model="mergeMethod" :disabled="onlyRead" style="width: 217px" @change="mergeChange">
                <el-option label="按文件合并" value="按文件合并">
                </el-option>
                <el-option label="按网格合并" value="按网格合并">
                </el-option>
              </el-select>
              <prompt-icon :content="mergeMethodTip"></prompt-icon>
            </el-form-item>
          </div>
          <div class="form-item-box">
            <div class="item-title">
              输出设置
            </div>
            <el-form-item label="存储类型">
              <el-select v-model="osgConversionParams.options.compactFormat" :disabled="onlyRead" placeholder=""
                         style="width: 217px">
                <el-option :value="false" label="散列"></el-option>
                <el-option :value="true" label="紧凑"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="输出路径">
              <el-input v-model="osgConversionParams.tileRootDir" :disabled="onlyRead" placeholder="请选择输出文件夹"
                        style="width: 217px"></el-input>
              <el-button :disabled="onlyRead" type="primary" @click="openFolderSelect('tileRootDir')">选择路径
              </el-button>
            </el-form-item>
          </div>
          <!-- 模型转换日志 -->
          <transition enter-active-class="animate__animated animate__fadeInLeft animate__faster"
                      leave-active-class="animate__animated animate__fadeOutRight animate__faster">
            <div v-if="journalShow" class="journal-box form-item-box">
              <div class="conver-log">
                <div class="item-title">{{ convertTitle }}</div>
                <div ref="logBox" class="log-box">
                  <el-alert v-for="(item, index) in conversionLog" :key="index" :closable="false" :title="item.info"
                            :type="item.type"></el-alert>
                </div>
              </div>
            </div>
          </transition>
          <div class="form-item-box" style="border:none">
            <el-form-item class="submit-btn">
              <el-button v-if="conversionLog.length > 0" class="grey-hover-primary-button"
                         @click="journalShow = !journalShow">
                日志
              </el-button>
              <el-button :loading="confirmLoading" type="primary" @click="clickConfirmBtn">转换</el-button>
              <el-button class="grey-hover-grey-button" @click="closeModelDialog">关闭</el-button>
            </el-form-item>
          </div>
        </el-form>
      </div>
    </div>
    <coordinate-set v-if="setShow" @close="setShow = false" @confirm="setCoordinate"></coordinate-set>
    <advanced-set v-if="advanceShow" :config.sync="osgConversionParams" :maxThread="maxThread"
                  :onlyRead="onlyRead" @confirm="advanceShow = false"></advanced-set>
    <osgb-update v-if="updateShow" :osgbConfig="osgConversionParams" @setTiles="getUpdateTiles"></osgb-update>
  </div>
</template>

<script>
// TODO::还需要加db文件的替换功能
const {ipcRenderer} = window.require("electron");
const path = window.require("path");
const os = window.require("os");
const fs = window.require("fs");
import coordinateSet from "../components/coordinateSet.vue";
import advancedSet from "./component/advancedSet.vue";
import promptIcon from "./component/promptIcon.vue";
import osgbUpdate from "./component/osgbUpdate.vue";

const SINGLEPARAME = {
  name: "",
  path: "",
  onlyRead: false,
  metadata: {
    proj: "",
    srs: "",
    originPoint: "",
    enuCenter: null,
  },
};
export default {
  data() {
    return {
      journalShow: false,
      updateShow: false,
      onlyRead: false,
      osgRootDirTip:
          "输入路径如果存在metadata.xml，程序将自动读入坐标信息；如果没有metadata.xml，可以在下边设置中进行自定义坐标参数，自定义坐标参数的优先级高于自动读入的metadata",
      reCreateNormalTip: "创建法线是为了在场景渲染时自定义光照",
      doubleSidedTip:
          "一般情况下，双面转换无需开启，但是对一些数据，可能有部分数据反面引起漏洞，可以开启该选项，解决此类问题。注意：开启此参数会导致一定的渲染性能损失。",
      mergeMethodTip:
          "当文件名称比较规则，所有的数据level都一致时，选择此选项。其余选择按网格合并",
      advanceShow: false,
      setShow: false,
      boxLoading: false,
      convertTitle: "转换中",
      confirmLoading: false,
      rebuildNormalLine: true,
      storageType: "",
      spatialReference: "",
      originCoordinates: "",
      mergeMethod: "按网格合并",
      maxThread: 0,
      osgConversionParams: {
        osgInput: {},
        tileRootDir: "",
        options: {
          indicesRoll: false, // 顶点是否反转
          removeNormal: true, // 是否删除法线
          reCreateNormal: true, //是否重建法线
          doubleSided: false, // 是否双面
          textureRatio: 0.5, //纹理大小
          textureThreshold: 1024,
          geometricErrorRatio: 38.4, //此参数调大，会更早加载下一级，对整体3D Tiles起作用
          isEllipsoid: true, //是否是椭球
          b3dmCoordinateWithEnu: false, //此项设置为True，可支持b3dm存储站心坐标系坐标，方便在前端调整3D Tiles的位置
          compactFormat: false, //设置为true是紧凑型，需使用服务工具启动服务进行解析，默认是散列存储
          encrypt: false,
          meshopt: false,
        },
        merge: {
          highQuality: true, // 在合并时处理速度约变为原来的2/3，虽然浪费时间，但图片质量会变高，解决图片合并后变暗的问题。
          mergeGridTileThreshold: 2, //多少格网合并
          thresholdLevelForRtt: 3, // 合并方式
          textureThreshold: 1024, // merge的纹理最大尺寸
          textureRatio: 0.5,
          simplifyThreshold: 30000, //抽到多少不再抽稀，30000指这么多三角面就不再合并
          mergeGridType: "byTdtGrid", //此项不加，为默认按照文件夹L层级合并
        },
        threads: 0,
      },
      inputSettings: [],
      activeButton: "",
      conversionLog: [],
      toolAddress: "",
      configPath: "",
      exePath: "",
      editingId: "",
      needUpdate: null,
    };
  },
  watch: {
    conversionLog(val) {
      this.$nextTick(() => {
        if (this.$refs.logBox) {
          this.$refs.logBox.scrollTo(0, this.$refs.logBox.scrollHeight);
        }
      });
    },
  },
  components: {
    coordinateSet,
    advancedSet,
    promptIcon,
    osgbUpdate,
  },
  methods: {
    getUpdateTiles(data) {
      this.needUpdate = data;
      this.updateShow = false;
    },
    updateOsgb() {
      if (!this.onlyRead) {
        this.$message.warning("请先导入osgb.conf文件");
        return;
      }
      this.updateShow = true;
    },
    // 导入osgb.conf文件
    importConf() {
      const option = {
        filters: [{name: "file", extensions: ["conf"]}],
      };
      ipcRenderer.send("show-open-dialog", option);
      ipcRenderer.once("show-open-dialog", (event, data) => {
        if (data.fileContent) {
          this.osgConversionParams = JSON.parse(data.fileContent);
          this.importInputSetting(this.osgConversionParams.osgInput);
          this.onlyRead = true;
        }
      });
    },

    // 处理导入的输入参数
    importInputSetting(data) {
      this.inputSettings = [];
      for (let i in data) {
        const item = data[i];
        const obj = {
          name: i,
          onlyRead: true,
          path: item.path,
          metadata: {
            ...item.metadata,
            originPoint: item.metadata.originPoint.join(","),
          },
        };
        this.inputSettings.push(obj);
      }
    },

    // 打开修改坐标系面板
    modifyCoordinateSystem(id) {
      this.setShow = true;
      this.editingId = id;
    },
    addInputSetting() {
      // 这里不能用解构的方式，解构只能进行浅拷贝
      // this.inputSettings.unshift({...SINGLEPARAME,id:this.$guid()})
      this.inputSettings.unshift({
        ...JSON.parse(JSON.stringify(SINGLEPARAME)),
        id: this.$guid(),
      });
    },
    removeInputSetting() {
      if (!this.inputSettings[0].onlyRead) {
        this.inputSettings.shift();
      }
    },
    /**
     * 设置坐标
     *
     * @param data 坐标数据
     * @param data.proj 投影信息
     */
    setCoordinate(data) {
      this.setShow = false;
      if (data.proj) {
        this.inputSettings = this.inputSettings.map((item) => {
          if (item.id === this.editingId) {
            item.metadata.proj = data.proj ? data.proj : "";
            item.metadata.srs = data.epsg ? data.epsg : "";
            item.originPoint = "";
          }
          return item;
        });
      }
      this.editingId = "";
    },
    /**
     * 合并方式变化时的处理函数
     *
     * @param val 合并方式，可选值为'按文件合并'或'按网格合并'
     */
    mergeChange(val) {
      switch (val) {
        case "按文件合并":
          delete this.osgConversionParams.merge.mergeGridType;
          break;
        case "按网格合并":
          this.osgConversionParams.merge.mergeGridType = "byTdtGrid";
      }
    },
    closeModelDialog() {
      this.$emit("close");
    },
    // 抽象出的检查一致性的函数
    checkUniformity(property) {
      if (this.inputSettings.length === 0) {
        // 如果inputSettings为空，直接返回false，这依赖于函数调用者的处理逻辑
        return false;
      }
      const referenceValue = this.inputSettings[0].metadata[property];
      // 使用every方法检查数组中每个元素的指定属性是否与第一个元素的属性值相同且不为空
      return this.inputSettings.every(
          (item) =>
              item.metadata[property] === referenceValue &&
              item.metadata[property] !== ""
      );
    },
    clickConfirmBtn() {
      let type = "normal";
      if (this.needUpdate && this.needUpdate.length > 0) {
        this.removeFolder();
        type = "replace";
      }
      this.conversionJudge(type);
    },
    // 移除对应的文件夹
    removeFolder() {
      let folderPaths = [];
      this.needUpdate.forEach((item) => {
        folderPaths.push({
          folderName: path.basename(path.resolve(item, "../../")),
          updatePath: path.basename(item),
        });
      });
      let inputKeys = Object.keys(this.osgConversionParams.osgInput);
      if (inputKeys.length > 1) {
        folderPaths.forEach((item) => {
          this.deleteFolderRecursive(
              path.resolve(
                  this.osgConversionParams.tileRootDir,
                  item.folderName,
                  item.updatePath
              )
          );
        });
      } else if (inputKeys.length === 1) {
        folderPaths.forEach((item) => {
          this.deleteFolderRecursive(
              path.resolve(this.osgConversionParams.tileRootDir, item.updatePath)
          );
        });
      }
    },
    /**
     * 递归删除文件夹及其所有内容
     *
     * 此方法会递归地删除指定路径下的所有文件和子文件夹，实现彻底清除
     * 主要用于需要彻底清理文件夹内容的场景
     *
     * @param {string} filePath - 待删除的文件夹路径
     */
    deleteFolderRecursive(filePath) {
      if (fs.existsSync(filePath)) {
        const files = fs.readdirSync(filePath);
        for (const file of files) {
          const curPath = path.join(filePath, file);
          const stats = fs.statSync(curPath);
          if (stats.isDirectory()) {
            this.deleteFolderRecursive(curPath); // 递归删除子文件夹
          } else {
            fs.unlinkSync(curPath); // 删除文件
          }
        }
        fs.rmdirSync(filePath);
      }
    },
    conversionJudge(type) {
      // 检查inputSettings是否为空，避免边界条件下的错误
      if (this.inputSettings.length === 0) {
        this.$message.error("输入设置为空，请先添加设置。");
        return;
      }
      // 判断多个数据的srs或者proj是否一致
      const isSrsSame = this.checkUniformity("srs");
      const isProjSame = this.checkUniformity("proj");
      if (!isSrsSame && !isProjSame) {
        this.$message.error("坐标系不一致，请检查！");
        return;
      }
      this.journalShow = true
      this.conversionLog = [];
      this.convertTitle = "转换中";
      this.startConversion(type);
    },
    /**
     * 打开文件夹选择对话框，并根据选择的文件夹进行后续操作。
     * @param {string} id - 识别ID，用于标识选择文件夹的具体用途。
     * @param {string} type - 类型标识，用于区分选择文件夹后的处理方式。
     */
    openFolderSelect(id, type) {
      const option = {
        title: `选择文件夹`,
        buttonLabel: "确定",
        properties: ["openDirectory"],
      };
      ipcRenderer.send("open-directory-dialog", option);
      ipcRenderer.once("open-directory-dialog", (event, data) => {
        if (type === "input") {
          const upperLevelDirectory = path.resolve(
              path.dirname(data.folderPath),
              "metadata.xml"
          );
          this.getXmlData({
            xmlUrl: upperLevelDirectory,
            id,
            folderPath: data.folderPath,
          });
        } else {
          this.osgConversionParams.tileRootDir = data.folderPath;
        }
      });
    },
    async getXmlData(data) {
      const {xmlUrl, id, folderPath} = data;
      const name = path.basename(path.resolve(folderPath, "../"));
      let srsElements, srsOriginElements;
      try {
        const xmlData = await this.$get(xmlUrl);
        const xmlDom = new DOMParser().parseFromString(xmlData, "text/xml");
        srsElements = xmlDom.getElementsByTagName("SRS");
        srsOriginElements = xmlDom.getElementsByTagName("SRSOrigin");
      } catch (error) {
        this.$message.error("获取或分析xml数据时出错" + error);
      }
      this.inputSettings = this.inputSettings.map((item) => {
        if (item.id === id) {
          item.name = name;
          item.path = folderPath;
          item.metadata.srs = srsElements ? srsElements[0].textContent : "";
          item.metadata.originPoint = srsOriginElements
              ? srsOriginElements[0].textContent
              : "";
        }
        return item;
      });
    },
    // 处理转换参数
    processParame() {
      let obj = {};
      this.inputSettings.forEach((item) => {
        if (item.path) {
          obj[item.name] = {
            path: item.path,
            metadata: {
              ...item.metadata,
              originPoint: item.metadata.originPoint.split(",").map(Number),
            },
          };
        }
      });
      this.osgConversionParams.osgInput = obj;
    },
    // 开始转换
    async startConversion(type) {
      this.processParame();
      this.confirmLoading = true;
      this.boxLoading = true;
      let option = {
        exeName: "osgTo3DTiles",
        configName: "OsgTo3DtilesConfig",
        config: JSON.stringify(this.osgConversionParams),
        isSave: true,
        type,
      };
      ipcRenderer.send("spawn-exe", option);
      ipcRenderer.on("spawn-exe", this.ipcListenOn);
    },
    /**
     * 监听 IPC 消息
     *
     * @param event IPC 事件名
     * @param data 接收到的数据
     */
    ipcListenOn(_, data) {
      this.boxLoading = false;
      if (typeof data === "string") {
        ipcRenderer.off("spawn-exe", this.ipcListenOn);
        this.convertTitle = "日志";
        this.confirmLoading = false;
      } else {
        this.conversionLog.push(data);
      }
    }
  },
  created() {
    this.inputSettings.unshift({
      ...JSON.parse(JSON.stringify(SINGLEPARAME)),
      id: this.$guid(),
    });
  },
  mounted() {
    if (process.env.NODE_ENV === "development") {
      this.maxThread = 4;
    } else {
      this.maxThread = os.cpus().length;
    }
    this.osgConversionParams.threads = this.maxThread;
  },
};
</script>
<style lang="less" scoped>
.input-set-box {
  margin-bottom: 10px
}

.input-set-box:nth-last-child(1) {
  margin-bottom: 0
}

.journal-box {
  position: absolute;
  background: #444444;
  width: 100%;
  height: calc(100% - 100px);
  left: 0;
  top: 46px
}

.conver-log {
  color: #fff;
  box-sizing: border-box;
  padding: 0 10px;
  height: calc(100% - 40px);

  &::-webkit-scrollbar {
    width: 4px;
    height: 4px;
    background: transparent;
  }

  &::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 4px;
  }

  &::-webkit-scrollbar-track {
    background: transparent;
  }

  &:hover::-webkit-scrollbar-thumb {
    background: hsla(0, 0%, 90%, 0.4);
  }

  &:hover::-webkit-scrollbar-track {
    background: transparent;
  }

  .log-box {
    width: 100%;
    height: 100%;
    overflow: hidden;
    overflow-y: scroll;

    &::-webkit-scrollbar {
      width: 4px;
      height: 4px;
      background: transparent;
    }

    &::-webkit-scrollbar-thumb {
      background: transparent;
      border-radius: 4px;
    }

    &::-webkit-scrollbar-track {
      background: transparent;
    }

    &:hover::-webkit-scrollbar-thumb {
      background: hsla(0, 0%, 90%, 0.4);
    }

    &:hover::-webkit-scrollbar-track {
      background: transparent;
    }
  }
}
</style>
